// $Id: AbstractObjectifyGenericDAO.java 99 2011-01-28 12:42:13Z E106005 $
package it.reichel.dao;

import com.google.appengine.api.datastore.EntityNotFoundException;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.Query;
import com.googlecode.objectify.helper.DAOBase;

import javax.persistence.Embedded;
import javax.persistence.Transient;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;

public abstract class AbstractObjectifyGenericDAO<T> extends DAOBase implements ObjectifyDAO<T>, Serializable {

    static final int BAD_MODIFIERS = Modifier.FINAL | Modifier.STATIC | Modifier.TRANSIENT;

    //@Autowired
    //private ObjectifyFactory objectifyFactory;

    static {
        // Registrar todas as classes (Entitys) aqui,
        // no spring-config.xml (usando plugin)
        // ou de preferência na classe que dá extends nesta aqui
        // ObjectifyService.register(entidade.class);
        // ...
    }

    protected Class<T> clazz = null;

    /**
     * Associa a entidade automagicamente
     */
    @SuppressWarnings( "unchecked" )
    public AbstractObjectifyGenericDAO() {
        clazz = (Class<T>) DAOUtil.getTypeArguments(AbstractObjectifyGenericDAO.class, this.getClass()).get(0);
        ObjectifyService.register(clazz);
    }


    /**
     * Associa a classe de domínio (bean) apenas informando a classe
     *
     * @param clazz
     */
    public AbstractObjectifyGenericDAO(Class<T> clazz) {
        this.clazz = clazz;
    }

    @Override
    public Key<T> put(T entity) {
        return ofy().put(entity);
    }

    @Override
    public Map<Key<T>, T> putAll(Iterable<T> entities) {

        return ofy().put(entities);
    }

    @Override
    public void delete(T entity) {
        ofy().delete(entity);
    }

    @Override
    public void deleteKey(Key<T> entityKey) {
        ofy().delete(entityKey);
    }

    @Override
    public void deleteAll(Iterable<T> entities) {
        ofy().delete(entities);
    }

    @Override
    public void deleteKeys(Iterable<Key<T>> keys) {
        ofy().delete(keys);
    }

    @Override
    public T get(Long id) throws EntityNotFoundException {
        return ofy().get(this.clazz, id);
    }

    @Override
    public T get(Key<T> key) throws EntityNotFoundException {
        return ofy().get(key);
    }

    /**
     * Convenience method to get all objects matching a single property
     *
     * @param propName
     * @param propValue
     * @return T matching Object
     */
    @Override
    public T getByProperty(String propName, Object propValue) {
        Query<T> q = ofy().query(clazz);
        q.filter(propName, propValue);
        return q.get();
    }

    @Override
    public List<T> listByProperty(String propName, Object propValue) {
        Query<T> q = ofy().query(clazz);
        q.filter(propName, propValue);
        return asList(q.list());
    }

    @Override
    public List<Key<T>> listKeysByProperty(String propName, Object propValue) {
        Query<T> q = ofy().query(clazz);
        q.filter(propName, propValue);
        return asKeyList(q.fetchKeys());
    }

    @Override
    public T getByExample(T exampleObj) {
        Query<T> queryByExample = buildQueryByExample(exampleObj);
        Iterable<T> iterableResults = queryByExample.list();
        Iterator<T> i = iterableResults.iterator();
        T obj = i.next();
        if (i.hasNext())
            throw new RuntimeException("Muito grande este resultset");
        return obj;
    }

    @Override
    public List<T> listByExample(T exampleObj) {
        Query<T> queryByExample = buildQueryByExample(exampleObj);
        return asList(queryByExample.list());
    }

    private List<T> asList(Iterable<T> iterable) {
        ArrayList<T> list = new ArrayList<T>();
        for (T t : iterable) {
            list.add(t);
        }
        return list;
    }

    private List<Key<T>> asKeyList(Iterable<Key<T>> iterableKeys) {
        ArrayList<Key<T>> keys = new ArrayList<Key<T>>();
        for (Key<T> key : iterableKeys) {
            keys.add(key);
        }
        return keys;
    }

    private Query<T> buildQueryByExample(T exampleObj) {
        Query<T> q = ofy().query(clazz);

        // Add all non-null properties to query filter
        for (Field field : clazz.getDeclaredFields()) {
            // Ignore transient, embedded, array, and collection properties
            if (field.isAnnotationPresent(Transient.class)
                    || (field.isAnnotationPresent(Embedded.class))
                    || (field.getType().isArray())
                    || (Collection.class.isAssignableFrom(field.getType()))
                    || ((field.getModifiers() & BAD_MODIFIERS) != 0))
                continue;

            field.setAccessible(true);

            Object value;
            try {
                value = field.get(exampleObj);
            } catch (IllegalArgumentException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
            if (value != null) {
                q.filter(field.getName(), value);
            }
        }

        return q;
    }

    /*
    private Objectify ofy(){
        Objectify ofy = objectifyFactory.begin();
        return ofy;
    }
    */
}

